#include "EngineD3D.h"

const D3DVERTEXELEMENT9 Vertex::d3dve[4] =
{
	{ 0,	0,	D3DDECLTYPE_FLOAT3,	D3DDECLMETHOD_DEFAULT,	D3DDECLUSAGE_POSITION,	0 },
	{ 0,	12,	D3DDECLTYPE_FLOAT3,	D3DDECLMETHOD_DEFAULT,	D3DDECLUSAGE_NORMAL,	0 },
	{ 0,	24,	D3DDECLTYPE_FLOAT2,	D3DDECLMETHOD_DEFAULT,	D3DDECLUSAGE_TEXCOORD,	0 },
	D3DDECL_END()
};

IDirect3DVertexDeclaration9* Vertex::d3dvd = NULL;

MouseTrack::~MouseTrack()
{

}

void MouseTrack::OnLeftButtonDown(WORD x, WORD y)
{

}

EngineD3D::EngineD3D(LPCSTR title)
	:EngineWin32(title)
{
	//create d3d object
	printf("create d3d object...");
	if(!(_d3d = Direct3DCreate9(D3D_SDK_VERSION)))
	{
		exit(-1);
	}
	printf("done.\n");

	//create device
	printf("create device...");
	DWORD behavior;
	D3DCAPS9 d3dcaps;
	ZeroMemory(&d3dcaps, sizeof(D3DCAPS9));
	if(FAILED(_d3d->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &d3dcaps)))
	{
		exit(-1);
	}
	if((d3dcaps.DevCaps&D3DDEVCAPS_HWTRANSFORMANDLIGHT)!=0)
	{
		if((d3dcaps.DevCaps&D3DDEVCAPS_PUREDEVICE)!=0)
		{
			behavior = D3DCREATE_HARDWARE_VERTEXPROCESSING|D3DCREATE_PUREDEVICE;
		}
		else
		{
			behavior = D3DCREATE_HARDWARE_VERTEXPROCESSING;
		}
	}
	else
	{
		behavior = D3DCREATE_SOFTWARE_VERTEXPROCESSING;
	}
	D3DPRESENT_PARAMETERS d3dpp;
	d3dpp.BackBufferWidth				= SCREEN_WIDTH;						// client width
	d3dpp.BackBufferHeight				= SCREEN_HEIGHT;					// client height
	d3dpp.BackBufferFormat				= D3DFMT_X8R8G8B8;					// color format
	d3dpp.BackBufferCount				= 1;								// 1 back buffer
	d3dpp.MultiSampleType				= D3DMULTISAMPLE_NONE;				// (default) un-supported
	d3dpp.MultiSampleQuality			= 0;								// (default) un-supported
	d3dpp.SwapEffect					= D3DSWAPEFFECT_DISCARD;			// entire client repaint required
	d3dpp.hDeviceWindow					= hwnd;								// set to focus
	d3dpp.Windowed						= TRUE;								// window app
	d3dpp.EnableAutoDepthStencil		= TRUE;								// auto depth-stencil buffer
	d3dpp.AutoDepthStencilFormat		= D3DFMT_D24S8;						// depth-stencil buffer format
	d3dpp.Flags							= 0;								// (default)
	d3dpp.FullScreen_RefreshRateInHz	= D3DPRESENT_RATE_DEFAULT;			// (default) window app must be 0
	d3dpp.PresentationInterval			= D3DPRESENT_INTERVAL_IMMEDIATE;	// (default)
	if(FAILED(_d3d->CreateDevice(
		D3DADAPTER_DEFAULT,	// use default adapter
		D3DDEVTYPE_HAL,		// use hal
		hwnd,				// focus window
		behavior,			// use checked vertex processing
		&d3dpp,				// present parameter
		&d3ddev				// device
	)))
	{
		exit(-1);
	}
	printf("done.\n");

	//create effects
	HR(D3DXCreateEffectFromFile(d3ddev, "MLP.fx", 0, 0, D3DXSHADER_DEBUG, 0, &_dxfx, &_dxbuf));
	if(_dxbuf)
	{
		printf("%s\n",(char*)_dxbuf->GetBufferPointer(), 0, 0);
		_dxbuf->Release();
	}

	//vertex binding
	HR(d3ddev->CreateVertexDeclaration(Vertex::d3dve,&Vertex::d3dvd));

	//Reference Texture
	HR(d3ddev->CreateTexture(SCREEN_WIDTH,SCREEN_HEIGHT,1,D3DUSAGE_RENDERTARGET,D3DFMT_R32F,D3DPOOL_DEFAULT,&_d3dtexRef,NULL));
	HR(_d3dtexRef->GetSurfaceLevel(0,&_d3dsurfRef));
	HR(d3ddev->CreateTexture(SCREEN_WIDTH,SCREEN_HEIGHT,1,D3DUSAGE_DYNAMIC,D3DFMT_R32F,D3DPOOL_SYSTEMMEM,&_d3dtexMT,NULL));
	HR(_d3dtexMT->GetSurfaceLevel(0,&_d3dsurfMT));
	D3DLOCKED_RECT lr;
	HR(_d3dsurfMT->LockRect(&lr,NULL,D3DLOCK_READONLY));
	ZeroMemory(lr.pBits,SCREEN_HEIGHT*lr.Pitch);
	HR(_d3dsurfMT->UnlockRect());
	_retrack = false;

}

EngineD3D::~EngineD3D()
{

}

void EngineD3D::Redraw()
{
	IDirect3DSurface9* d3dsurfBack;
	d3ddev->GetRenderTarget(0,&d3dsurfBack);

	HR(d3ddev->Clear(0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, 0x00000000, 1.0f, 0));

	d3ddev->SetRenderTarget(0,_d3dsurfRef);
	HR(d3ddev->Clear(0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, 0x00000000, 1.0f, 0));

	HR(d3ddev->BeginScene());

	//view
	D3DXMATRIX mView;
	D3DXMatrixLookAtLH(&mView,&v3ViewEye,&v3ViewTarget,&v3ViewUp);
	
	for(list<Geometry*>::iterator ig=geos.begin(); ig!=geos.end(); ig++)
	{
		Geometry* geo = *ig;

		D3DVERTEXBUFFER_DESC d3dvbdesc;
		geo->d3dvb->GetDesc(&d3dvbdesc);
		int nv = d3dvbdesc.Size/sizeof(Vertex);
		D3DINDEXBUFFER_DESC d3dibdesc;
		geo->d3dib->GetDesc(&d3dibdesc);
		int np = d3dibdesc.Size/sizeof(WORD);

		auto DrawPass = [=](int idx)
		{
			HR(_dxfx->BeginPass(idx));
			HR(d3ddev->SetStreamSource(0,geo->d3dvb,0,sizeof(Vertex)));
			HR(d3ddev->SetIndices(geo->d3dib));
			HR(d3ddev->SetVertexDeclaration(Vertex::d3dvd));
			HR(d3ddev->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,0,0,nv,0,np));
			HR(_dxfx->EndPass());
		};

		HR(_dxfx->SetTechnique("MultiLightPass"));

		HR(_dxfx->Begin(NULL, 0));

		D3DXMATRIX mPos = geo->mWorld;
		D3DXMATRIX mTrans = mPos*mView*mProj;
		D3DXMATRIX mNorm;
		D3DXMatrixInverse(&mNorm,NULL,&mPos);
		D3DXMatrixTranspose(&mNorm,&mNorm);

		d3ddev->SetRenderTarget(0,_d3dsurfRef);

		HR(_dxfx->SetMatrix("mTrans", &mTrans));

		float buf = 0.0f;
		memcpy(&buf,&geo,4);
		HR(_dxfx->SetFloat("fRef",buf));

		DrawPass(0);

		_retrack = true;

		d3ddev->SetRenderTarget(0,d3dsurfBack);

		HR(_dxfx->SetMatrix("mNorm",&mNorm));
		HR(_dxfx->SetMatrix("mPos",&mPos));

		HR(_dxfx->SetValue("f3View",(void*)&v3ViewEye,sizeof(D3DXVECTOR3)));

		HR(_dxfx->SetTexture("tex",geo->d3dtex));
		HR(_dxfx->SetVector("f4Mate",&geo->v4Mate));

		HR(_dxfx->SetValue("dl",&sun,sizeof(DirectLight)));

		DrawPass(1);

		d3ddev->SetRenderState(D3DRS_ZWRITEENABLE,FALSE);
		d3ddev->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
		d3ddev->SetRenderState(D3DRS_BLENDOP,D3DBLENDOP_ADD);
		d3ddev->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE);
		d3ddev->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);
		
		for(list<PointLight*>::iterator il=lights.begin(); il!=lights.end(); il++)
		{
			PointLight* lit = *il;
			
			HR(_dxfx->SetValue("pl",lit,sizeof(PointLight)));

			DrawPass(2);
		}
		d3ddev->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
		d3ddev->SetRenderState(D3DRS_ZWRITEENABLE,TRUE);

		HR(_dxfx->End());
	}

	HR(d3ddev->EndScene());

	HR(d3ddev->Present(NULL,NULL,NULL,NULL));

	HR(d3dsurfBack->Release());
}

Geometry* EngineD3D::_RetrieveGeometry(WORD x, WORD y)
{
	if(_retrack)
	{
		HR(d3ddev->GetRenderTargetData(_d3dsurfRef,_d3dsurfMT));
		_retrack = false;
	}
	D3DLOCKED_RECT lr;
	HR(_d3dsurfMT->LockRect(&lr,NULL,D3DLOCK_READONLY));
	Geometry* geo = *(Geometry**)((char*)lr.pBits+y*lr.Pitch+x*4);
	HR(_d3dsurfMT->UnlockRect());
	return geo;
}

void EngineD3D::OnLeftButtonDown(WORD x, WORD y)
{
	Geometry* geo = _RetrieveGeometry(x,y);
	if(geo!=NULL&&geo->mt!=NULL)
	{
		geo->mt->OnLeftButtonDown(x,y);
	}
}
